#include "utility.h"
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>


int isFileVersioned(const char *fileName)
{
	int isVersioned = 0;
	char *filePath = generateVCName(fileName);
	if(!filePath)
		return isVersioned;

	struct stat fileStatus;
	if( stat(filePath, &fileStatus) == 0)
		isVersioned = 1;

	free(filePath);
	return isVersioned;
}

/* Check if a file exists */
int fileExists(const char *file)
{
	struct stat fileStatus;
	if( stat(file, &fileStatus) != 0)
		return 0;
	else
		return 1;
}

int firstCheckin(const char *fileName)
{
	int result = 0;

	struct versionInfo ver;
	char *vcFile = generateVCName(fileName);
	char *logFile = generateLogName(fileName);

	if(!vcFile || !logFile)
	{
		printf("Allocation error");
		return result;
	}

	if(getenv("USER") == NULL)
		printf("Failed to optain username from environment variable.\n");

	else
	{
		ver.userName = malloc( (1 + strlen(getenv("USER"))) * sizeof(char) );
		strcpy(ver.userName, getenv("USER"));

		ver.inf.msg_size = getLongMessage(&ver.msg);

		ver.inf.version = 1;
		ver.inf.committed = time(NULL);
		ver.inf.locked = 0;
		ver.inf.usr_size = strlen(ver.userName);
		ver.inf.lock_usr_size = 0;
		ver.lockUser = NULL;

		writeVersion(logFile, &ver);

		if(copyFile(fileName, vcFile) == 0)
		{
#if DEBUG
			fprintf(stderr, "Could not move file from %s to %s", fileName, vcFile);
#endif
		}
		else
		{
			remove(fileName);

			result = 1;
		}

		free(ver.msg);
		free(ver.userName);
	}

	free(logFile);
	free(vcFile);
	return result;
}

int checkIn(const char* fileName)
{
	int version = 0;
	char *logFileName = generateLogName(fileName);
	if(!logFileName)
	{
		printf("Allocation error has occured.");
		return version;
	}

	struct versionInfo latest;
	if(readVersion(logFileName, &latest, -1) == 0)
	{
		free(logFileName);
		return version;
	}

	if(ownLock(&latest))
	{
		char *vcName = generateVCName(fileName);
		char *patchName = generateVerionPatchName(fileName, latest.inf.version);
		if(createPatch(fileName, vcName, patchName))
		{
			removeLock(logFileName);				//-----------------
			if(latest.userName != NULL) free(latest.msg);
			if(latest.userName != NULL) free(latest.userName);
			if(latest.lockUser != NULL) free(latest.lockUser);
			latest.lockUser = NULL;

			if(getenv("USER") == NULL)
				printf("Failed to obtain username.");
			else
			{
				latest.userName = malloc( (1 + strlen(getenv("USER"))) * sizeof(char) ); 	//-----------------
				strcpy(latest.userName, getenv("USER"));

				latest.inf.msg_size = getLongMessage(&latest.msg);

				latest.inf.version++;
				latest.inf.committed = time(NULL);
				latest.inf.locked = 0;
				latest.inf.usr_size = strlen(latest.userName);
				latest.inf.lock_usr_size = 0;

				if(writeVersion(logFileName, &latest))
				{
					remove(vcName);

					copyFile(fileName, vcName);
					remove(fileName);

					version = latest.inf.version;
				}

				free(latest.msg);
				free(latest.userName);
			}

			free(vcName);
		}

		free(patchName);
	}

	free(logFileName);
	return version;
}

int checkOut(const char* fileName)
{
	int version = 0;
	char *logName = generateLogName(fileName);
	struct versionInfo ver;
	if(readVersion(logName, &ver, -1) == 0)
		return version;

	char *vcName = generateVCName(fileName);

	if(ver.inf.locked == 0 && getenv("USER") != NULL)
	{
		ver.inf.locked = 1;

		ver.inf.lock_usr_size = strlen(getenv("USER"));
		ver.lockUser = (char*)malloc(ver.inf.lock_usr_size*sizeof(char));
		strcpy(ver.lockUser, getenv("USER"));

		if( removeLastVersion(logName) != 0
			&& writeVersion(logName, &ver) != 0
			&& copyFile(vcName, fileName) != 0 )
		{
			version = ver.inf.version;
		}
	}

	free(vcName);
	free(logName);
	return version;
}

char* generateVCName(const char* fileName)
{
	int size = 5 + strlen(fileName);
	size *= sizeof(char);
	char *vcName = (char*)malloc( size );
	if( !vcName )
		return NULL;

	snprintf(vcName, size, "VC/%s", fileName);
	return vcName;
}

char* generateLogName(const char* fileName)
{
	int size = 8 + strlen(fileName);
	size *= sizeof(char);
	char *logFile = (char*)malloc( size );
	if( !logFile )
		return NULL;

	snprintf(logFile, size, "VC/%s.log", fileName);
	return logFile;
}

char* generateVerionPatchName(const char* fileName, int version)
{
	int size = 9 + strlen(fileName);
	size *= sizeof(char);
	char *patchName = (char*)malloc( size );
	if(!patchName)
		return NULL;

	snprintf(patchName, size, "VC/%s.%d", fileName, version);
	return patchName;
}

int getLongMessage(char** msg)
{
	printf("Enter Log Message (^D or <CNTRL>+D when done):\n");
	unsigned int size = 0;
	unsigned int capacity = BUFF_STEP;
	*msg = malloc( capacity * sizeof(char) );
	(*msg)[0] = '\0';
	char tmp[BUFF_STEP];
	while ( fgets(tmp, BUFF_STEP, stdin) != NULL )
	{
		if(size + strlen(tmp) >= capacity - 1)
		{
			char *newBuf = realloc(*msg, capacity + BUFF_STEP);
			if(newBuf)
			{
				*msg = newBuf;
				capacity += BUFF_STEP;
			}
			else
				break;
		}
		strcat(*msg, tmp);
		size += strlen(tmp);
		if((*msg)[size-1] != '\n')
			break;
	}

	return size;
}

int readVersion(const char* logFile, struct versionInfo *ver, int version)
{
	FILE *fp = fopen(logFile, "r+");
	if(fp == NULL)
	{
#if DEBUG
		perror("Could not open logfile");
#endif
		return 0;
	}

	fseek(fp, -sizeof(struct inf), SEEK_END);

	fread((void*)&(*ver).inf, sizeof(struct inf), 1, fp);

	int pos = ver->inf.msg_size
			+ ver->inf.usr_size
			+ ver->inf.lock_usr_size
			+ sizeof(struct inf);

	fseek(fp, -pos, SEEK_END);

	if(version > 0 && version < ver->inf.version)
	{
		while(ver->inf.version != version)
		{
			if(fseek(fp, -sizeof(struct inf), SEEK_CUR) != 0)
				break;

			fread((void*)&(*ver).inf, sizeof(struct inf), 1, fp);

			int pos = ver->inf.msg_size
                        	+ ver->inf.usr_size
                        	+ ver->inf.lock_usr_size
                        	+ sizeof(struct inf);

			fseek(fp, -pos, SEEK_CUR);
		}
	}

	if( version > 0 && ver->inf.version != version )
	{
		printf("Version %d was not found.", version);
		ver->msg = NULL;
		ver->lockUser = NULL;
		ver->userName = NULL;
		fclose(fp);
		return 0;
	}

	ver->userName = NULL;
	ver->lockUser = NULL;
	ver->msg = NULL;

	if(ver->inf.lock_usr_size > 0)
	{
		ver->lockUser = malloc(ver->inf.lock_usr_size+1);
		fread(ver->lockUser, ver->inf.lock_usr_size, 1, fp);
		ver->lockUser[ver->inf.lock_usr_size] = '\0';
	}
	if(ver->inf.msg_size > 0)
	{
		ver->msg = malloc(ver->inf.msg_size+1);
		fread(ver->msg, ver->inf.msg_size, 1, fp);
		ver->msg[ver->inf.msg_size] = '\0';
	}
	if(ver->inf.usr_size > 0)
	{
		ver->userName = malloc(ver->inf.usr_size);
		fread(ver->userName, ver->inf.usr_size, 1, fp);
		ver->userName[ver->inf.usr_size] = '\0';
	}

	fclose(fp);
	return ver->inf.version;
}


int writeVersion(const char* logFile, struct versionInfo *ver)
{
	FILE *fp = fopen(logFile, "a");
	if(fp == NULL)
	{
		printf("Could not write to log file.\n");
#if DEBUG
		perror("fopen error: ");
#endif
		return 0;
	}

	if(ver->inf.lock_usr_size > 0 )
		fwrite((void*)ver->lockUser, ver->inf.lock_usr_size, 1, fp);
	if(ver->inf.msg_size > 0 )
                fwrite((void*)ver->msg, ver->inf.msg_size, 1, fp);
	if(ver->inf.usr_size > 0 )
                fwrite((void*)ver->userName, ver->inf.usr_size, 1, fp);

	fwrite((void*)&(ver->inf), sizeof(struct inf), 1, fp);

	fclose(fp);

	chmod(logFile, S_RWALL );

	return 1;
}

int removeLastVersion(const char* logFile)
{
	struct versionInfo ver;
	if( readVersion(logFile, &ver, -1) == 0)
		return 0;

	struct stat fileSize;
        if(stat(logFile, &fileSize) != 0)
                        return 0;

        off_t newSize = fileSize.st_size
        			- sizeof(struct inf)
				- ver.inf.msg_size
				- ver.inf.lock_usr_size
				- ver.inf.usr_size;

	truncate(logFile, newSize);
	return 1;
}

int removeLock(const char* logFile)
{
	struct versionInfo ver;
	if(readVersion(logFile, &ver, -1) == 0)
		return 0;

	if(ver.inf.locked)
	{
		removeLastVersion(logFile);

		ver.inf.locked = 0;
		ver.inf.lock_usr_size = 0;
		free(ver.lockUser);
		ver.lockUser = NULL;

		if( writeVersion(logFile, &ver) == 0)
			return 0;
	}

	return 1;
}

int ownLock(const struct versionInfo *ver)
{
	char *usrName = getenv("USER");

	if(usrName == NULL)
	{
#if DEBUG
		fprintf(stderr, "ownLock(): Could not aquire username.\n");
#endif
		return 0;
	}


	if( ver->lockUser == NULL )
		return 0;

	if( strcmp(usrName, ver->lockUser) == 0 )
		return 1;

	return 0;
}

int createPatch(const char* newVersion, const char* oldVersion, const char* patchName)
{
	int size = strlen(newVersion)
		+ strlen(oldVersion)
		+ strlen(patchName) + 10;

	size *= sizeof(char);

	char *cmd = malloc(size);
	snprintf(cmd, size, "diff ./%s ./%s", newVersion, oldVersion);

	FILE *fp = fopen(patchName, "w+");

	if( fp == NULL )
	{
#if DEBUG
		perror("Could not create patch file for writing");
#endif
		if(cmd) free(cmd);
		return 0;
	}

	FILE *diff = popen(cmd, "r");

	if(diff == NULL)
	{
#if DEBUG
		perror("Cannot runn patch utility");
#endif
		fclose(fp);
		remove(patchName);
		free(cmd);
		return 0;
	}

	char buff[256];
	size = 0;
	while( (size = fread(buff, 1, 255, diff)) > 0)
		fwrite(buff, 1, size, fp);


	pclose(diff);
	fclose(fp);

	chmod(patchName, S_RWALL);

	free(cmd);
	return 1;
}

int copyFile(const char* src, const char* dst)
{
	FILE *fp_src = fopen(src, "r");
	FILE *fp_dst = fopen(dst, "w+");

	if(fp_dst == NULL)
	{
		remove(dst);
		fp_dst = fopen(dst, "w+");
	}

	if(fp_src == NULL || fp_dst == NULL)
	{
#if DEBUG
		perror("copyFile() could not open file");
#endif
		if(fp_src) fclose(fp_src);
		if(fp_dst) fclose(fp_dst);
		return 0;
	}

	char buffer[BUFF_STEP];
	int size = 0;
	while( (size = fread(buffer, 1, 255, fp_src)) > 0 )
		fwrite(buffer, 1, size, fp_dst);


	fflush(fp_dst);
	fclose(fp_src);
	fclose(fp_dst);

	chmod(dst, S_RWALL);

	return 1;
}

int getFileVersion(const char* fileName, int version, const char* dst)
{
	char *vcName = generateVCName(fileName);
	char *logName = generateLogName(fileName);

	struct versionInfo ver;
	if(readVersion(logName, &ver, -1) == 0)
	{
		free(vcName);
		free(logName);
		return 0;
	}	
	if(version > ver.inf.version)
	{
		free(vcName);
		free(logName);
		if(ver.msg) free(ver.msg);
		if(ver.userName) free(ver.userName);
		if(ver.lockUser) free(ver.lockUser);
		return 0;
	}

	if(copyFile(vcName, dst) == 0)
	{
		free(vcName);
		free(logName);
		if(ver.msg) free(ver.msg);
		if(ver.userName) free(ver.userName);
		if(ver.lockUser) free(ver.lockUser);
		return 0;
	}

	char *cmd = malloc( sizeof(char) * (strlen(vcName)*2 + 38)  );

	if(!cmd)
	{
#if DEBUG
		fprintf(stderr, "Could not allocate memory for cmd.\n");
#endif
		return 0;
	}

	for(int i = ver.inf.version-1; i >= version; i--)
	{
		sprintf(cmd, "(patch -i %s.%d %s 2>&1) > /dev/null", vcName, i, dst);

		system(cmd);
	}

	chmod(dst, S_IRUSR | S_IRGRP | S_IROTH );

	if(cmd) free(cmd);
	if(logName) free(logName);
	if(vcName) free(vcName);
	return version;
}

int printLog(const char* fileName)
{
	struct versionInfo ver;
	int version = -1;
	char *logName = generateLogName(fileName);

	do {
		if(readVersion(logName, &ver, version) == 0)
		{
			free(logName);
			return 0;
		}

		version = ver.inf.version-1;

		printf("Version: %d\nSubmitted by: %s\nSubmission date: %s",
			ver.inf.version, ver.userName, ctime(&ver.inf.committed));
		if(ver.inf.locked) printf("Currently Locked by: %s\n", ver.lockUser);
		printf("Comments:\n%s\n", ver.msg);
		printf("--------------------------------------------------------------\n\n");

		if(ver.msg) free(ver.msg);
		if(ver.userName) free(ver.userName);
		if(ver.lockUser) free(ver.lockUser);

	}while(version > 0);

	free(logName);
	return 1;
}


//CHECK v1 > 0, v2 > 0
//CHECK v1 <= CURRENT VERSION, v2 <= CURRENT VERSION
int printPatch(const char *fileName, int v1, int v2)
{
	char *v1Tmp = malloc(15*sizeof(char));
	char *v2Tmp = malloc(15*sizeof(char));
	char *vcName = generateVCName(fileName);

	if(!v1Tmp || !v2Tmp || !vcName)
		return 0;

	strcpy(v1Tmp, "VC/tmp.XXXXXX");
	strcpy(v2Tmp, "VC/tmp.XXXXXX");


	int v1File = mkstemp(v1Tmp);
	int v2File = mkstemp(v2Tmp);

	if(v1File == -1 || v2File == -1)
	{
#if DEBUG
		fprintf(stderr, "Could not generate tempurary file names.");
#endif
		if(v1File != -1) close(v1File);
		if(v2File != -1) close(v2File);
		free(v1Tmp);
		free(v2Tmp);
		free(vcName);
		return 0;
	}

	close(v1File);
	close(v2File);

	int latest = getLatestVersion(fileName);

	if(v1 <= 0 || v1 > latest
		|| v2 <= 0 || v2 > latest)

	{
		remove(v1Tmp);
		remove(v2Tmp);
		free(v1Tmp);
		free(v2Tmp);
		free(vcName);
		printf("Version outside of version range.\n");
		return 0;
	}

	getFileVersion(fileName, v1, v1Tmp);
	getFileVersion(fileName, v2, v2Tmp);

	char *cmd = malloc( sizeof(char)*(7 + strlen(v1Tmp) + strlen(v2Tmp)) );

	sprintf(cmd, "diff %s %s", v1Tmp, v2Tmp);

	system(cmd);

	if(remove(v1Tmp) != 0)
		perror("Could not remove tempurary file");
	if(remove(v2Tmp) != 0)
		perror("Could not remove tempurary file");

	free(cmd);
	free(vcName);
	free(v2Tmp);
	free(v1Tmp);

	return 1;
}


int getLatestVersion(const char* fileName)
{
	char* logFile = generateLogName(fileName);

	struct versionInfo ver;
	readVersion(logFile, &ver, -1);

	if(ver.msg) free(ver.msg);
	if(ver.userName) free(ver.userName);
	if(ver.lockUser) free(ver.lockUser);

	free(logFile);

	return ver.inf.version;
}
